home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 4 / Info_Mac IV CD-ROM (Pacific HiTech Inc.)(August 1994).iso / Development / Source / DBL Pascal Library / CallChain ƒ / CallChain.p < prev    next >
Text File  |  1992-11-01  |  9KB  |  340 lines

  1. unit CallChain;
  2.  
  3. interface
  4.  
  5. {Returns True so long as depth is within stack, and stack is uncorrupted.}
  6.     function GetCallerInfo (depth: Integer; var frame: Ptr; var procName: Str255; var offset: Integer): Boolean;
  7.  
  8. {Returns True if we got a complete trace.}
  9.     function GetStackTrace (startingDepth: Integer; dest: CharsHandle; var destSize: Size): Boolean;
  10.  
  11. implementation
  12.  
  13. {A procedure begins (optionally) with a LINK A6,#nnnn instruction, and ends with}
  14. {one of (a) an RTS, (b) a JMP (A0), or (c) an RTD #nnnn. The ending instruction is}
  15. {followed (optionally) by a name and constant data. The name can be in any of three}
  16. {formats (described below). The constant data consists of a word-length byte count}
  17. {followed by the actual data; the count is word-aligned following the name.}
  18. {}
  19. {The name formats are (a) fixed 8-byte, (b) fixed 16-byte, and (c) variable. The}
  20. {16-byte format is used specifically for short class.method names in Object Pascal.}
  21. {Valid characters are in the set [a–zA–Z0–9_%.] (blanks are used to pad fixed-length names).}
  22. {}
  23. {Fixed-8:}
  24. {    First character in range $20–$7F, ignoring MSB.}
  25. {    MSB of second character is always clear.}
  26. {    Name is eight characters long, with trailing blanks trimmed.}
  27. {Fixed-16:}
  28. {    First character in range $20–$7F, ignoring MSB.}
  29. {    MSB of second character is always set.}
  30. {    Stored as two eight-byte names; method followed by class.}
  31. {    Name is constructed as CLASS.METHOD – must insert period between}
  32. {     parts of name after stripping trailing blanks.}
  33. {Variable:}
  34. {    First byte in range $80–$9F, including MSB.}
  35. {    If first byte is $80, then second byte contains actual length in range $01–$FF.}
  36. {    If first byte is $81–$9F, then clearing MSG gives actual length in range $01–$1F.}
  37. {    Length byte(s) is (are) followed by name, without padding.}
  38.  
  39.     function CurrentA6: Ptr;
  40.     inline
  41.         $2E8E;    {move.l a6,(sp)}
  42.  
  43.     function NextFrame (whichFrame: univ Ptr): Ptr;
  44.     inline
  45.         $205F,    {movea.l (sp)+,a0}
  46.         $2E90;    {move.l (a0),(sp)}
  47.  
  48.     function CallerRA (whichFrame: univ Ptr): Ptr;
  49.     inline
  50.         $205F,            {movea.l (sp)+,a0}
  51.         $2EA8, $0004;    {move.l 4(a0),(sp)}
  52.  
  53.     function CurrentSP: Ptr;
  54.     inline
  55.         $2E8F;    {move.l sp,(sp)}
  56.  
  57.     function AddressInStack (theAddress: univ Longint): Boolean;
  58.         type
  59.             LongPtr = ^Longint;
  60.         const
  61.             CurStackBase = $908;
  62.     begin
  63.         AddressInStack := (theAddress <= LongPtr(CurStackBase)^) & (theAddress >= Longint(CurrentSP));
  64.     end;
  65.  
  66.     type
  67.         IntPtr = ^Integer;
  68.  
  69.     const
  70.         LINKA6_instruction = $4E56;    {this is a two-word instruction}
  71.         RTS_instruction = $4E75;        {this is a one-word instruction}
  72.         JMPatA0_instruction = $4ED0;    {this is a one-word instruction}
  73.         RTD_instruction = $4E74;        {this is a two-word instruction}
  74.         HowFar = 32766;
  75.  
  76.     function MaybeFindName (startingAt: univ Longint): Ptr;
  77.         var
  78.             where, stopAt: Longint;
  79.     begin
  80.         stopAt := startingAt + HowFar;
  81.         where := startingAt;
  82.         MaybeFindName := nil;
  83.         while where < stopAt do
  84.             begin
  85.                 case IntPtr(where)^ of
  86.                     LINKA6_instruction: 
  87.                         Leave;
  88.                     RTS_instruction, JMPatA0_instruction: 
  89.                         begin
  90.                             MaybeFindName := Ptr(where + SIZEOF(Integer));
  91.                             Leave;
  92.                         end;
  93.                     RTD_instruction: 
  94.                         begin
  95.                             MaybeFindName := Ptr(where + 2 * SIZEOF(Integer));
  96.                             Leave;
  97.                         end;
  98.                     otherwise
  99.                         ;
  100.                 end;
  101.                 where := where + SIZEOF(Integer);
  102.             end;
  103.     end; {MaybeFindName}
  104.  
  105.     function AddressInHeap (where: univ Ptr): Boolean;
  106.         var
  107.             theZone: THz;
  108.     begin
  109.         theZone := GetZone;
  110.         AddressInHeap := (ORD(where) >= ORD(@theZone^.heapData)) & (ORD(where) < ORD(theZone^.bkLim));
  111.     end;
  112.  
  113.     type
  114.         CharPtr = ^SignedByte;
  115.  
  116.     function GetName (where: univ Longint; var theName: Str255): Boolean;
  117.  
  118.         function CopyName (start: univ Longint; expectedLength: Integer; howManyMSBs: Integer; dest: StringPtr): Boolean;
  119.  
  120.             procedure Fail;
  121.             begin
  122.                 CopyName := False;
  123.                 Exit(CopyName);
  124.             end; {Fail}
  125.  
  126.             const
  127.                 ValidChars = ['a'..'z', 'A'..'Z', '0'..'9', '_', '%', '.', ' '];
  128.             var
  129.                 where: Longint;
  130.                 i, actualLength: Integer;
  131.                 theChar: Char;
  132.  
  133.         begin {CopyName}
  134.             where := start;
  135.             actualLength := expectedLength;
  136.             for i := 1 to expectedLength do
  137.                 begin
  138.                     theChar := Char(CharPtr(where)^);
  139.                     if (i > howManyMSBs) & BTST(theChar, 7) then
  140.                         Fail;
  141.                     theChar := Char(BAND(theChar, $7F));
  142.                     if theChar in ValidChars then
  143.                         begin
  144.                             if (actualLength = expectedLength) & (theChar = ' ') then
  145.                                 actualLength := i - 1;
  146. {$PUSH}
  147. {$R-}
  148.             {Turn off range checking because string length isn’t yet set…}
  149.                             dest^[i] := theChar;
  150. {$POP}
  151.                             where := where + SIZEOF(SignedByte);
  152.                         end
  153.                     else
  154.                         Fail;
  155.                 end;
  156. {$PUSH}
  157. {$R-}
  158.             dest^[0] := CHR(actualLength);
  159. {$POP}
  160.             CopyName := True;
  161.         end; {CopyName}
  162.  
  163.         procedure NotAName;
  164.         begin
  165.             theName := '';
  166.             GetName := False;
  167.             Exit(GetName);
  168.         end;
  169.  
  170.         var
  171.             name2: string[8];
  172.  
  173.     begin {GetName}
  174.         GetName := True;
  175.         if not AddressInHeap(where) then
  176.             NotAName;
  177.         case BAND(CharPtr(where)^, $FF) of
  178.             $20..$7F, $A0..$FF: 
  179.                 if BTST(CharPtr(where + SIZEOF(SignedByte))^, 7) then
  180.                     begin    {fixed-16}
  181.                         if CopyName(where, 8, 2, @name2) & CopyName(where + 8 * SIZEOF(SignedByte), 8, 0, @theName) then
  182.                             theName := CONCAT(theName, '.', name2)
  183.                         else
  184.                             NotAName;
  185.                     end
  186.                 else
  187.                     begin    {fixed-8}
  188.                         if not CopyName(where, 8, 1, @theName) then
  189.                             NotAName;
  190.                     end;
  191.             $80: 
  192.                 begin    {variable, 1–255 char}
  193.                     if not CopyName(where + 2 * SIZEOF(SignedByte), BAND(CharPtr(where + SIZEOF(SignedByte))^, $7F), 0, @theName) then
  194.                         NotAName;
  195.                 end;
  196.             $81..$9F: 
  197.                 begin    {variable, 1–31 char}
  198.                     if not CopyName(where + SIZEOF(SignedByte), BAND(CharPtr(where)^, $7F), 0, @theName) then
  199.                         NotAName;
  200.                 end;
  201.             otherwise
  202.                 NotAName;
  203.         end;
  204.     end; {GetName}
  205.  
  206.     function MaybeFindEntry (startingAt: univ Longint): Ptr;
  207.         var
  208.             where, stopAt: Longint;
  209.     begin
  210.         stopAt := startingAt - HowFar;
  211.         where := startingAt;
  212.         MaybeFindEntry := nil;
  213.         while where > stopAt do
  214.             begin
  215.                 case IntPtr(where)^ of
  216.                     LINKA6_instruction: 
  217.                         if IntPtr(where + SIZEOF(Integer))^ < 0 then
  218.                             begin    {• This could give spurious positives.}
  219.                                 MaybeFindEntry := Ptr(where);
  220.                                 Leave;
  221.                             end;
  222.                     RTS_instruction, JMPatA0_instruction, RTD_instruction: 
  223.                         Leave;        {• This could give spurious negatives.}
  224.                     otherwise
  225.                         ;
  226.                 end;
  227.                 where := where - SIZEOF(Integer);
  228.             end;
  229.     end; {MaybeFindEntry}
  230.  
  231.     function GetCallerInfo (depth: Integer; var frame: Ptr; var procName: Str255; var offset: Integer): Boolean;
  232.  
  233.         procedure Fail;
  234.         begin
  235.             GetCallerInfo := False;
  236.             frame := nil;
  237.             procName := '';
  238.             offset := 0;
  239.             Exit(GetCallerInfo);
  240.         end; {Fail}
  241.  
  242.         var
  243.             frameAddress, procReturn, procEntry: Ptr;
  244.             i: Integer;
  245.  
  246.     begin {GetCallerInfo}
  247.         GetCallerInfo := True;    {We’ll change this later, if we fail…}
  248.         frameAddress := CurrentA6;
  249.         for i := 1 to depth do
  250.             if not AddressInStack(frameAddress) then
  251.                 Fail
  252.             else
  253.                 frameAddress := NextFrame(frameAddress);
  254.         frame := frameAddress;
  255.         procReturn := CallerRA(frameAddress);
  256.         if not GetName(MaybeFindName(procReturn), procName) then
  257.             Fail;
  258.         procEntry := MaybeFindEntry(procReturn);
  259.         if procEntry <> nil then
  260.             offset := ORD(procReturn) - ORD(procEntry)
  261.         else
  262.             offset := 0;
  263.     end; {GetCallerInfo}
  264.  
  265.     function GetStackTrace (startingDepth: Integer; dest: CharsHandle; var destSize: Size): Boolean;
  266.  
  267.         procedure MakeHex (num: univ Longint; dest: CharsPtr; digits: Integer);
  268.             var
  269.                 i, digit: Integer;
  270.         begin
  271.             for i := digits - 1 downto 0 do
  272.                 begin
  273.                     digit := num mod 16;
  274.                     if digit < 10 then
  275.                         dest^[i] := CHR(digit + ORD('0'))
  276.                     else
  277.                         dest^[i] := CHR(digit + ORD('A') - 10);
  278.                     num := num div 16;
  279.                 end;
  280.         end; {MakeHex}
  281.  
  282.         const
  283.             addSize = 15;    {8 digits, space, '+', 4 digits, CR}
  284.  
  285.         var
  286.             i: Integer;
  287.             aFrame: Ptr;
  288.             aName: Str255;
  289.             aNameLength: Integer;
  290.             anOffset: Integer;
  291.             stillOK: Boolean;
  292.             outSize: Size;
  293.             outPtr: CharPtr;
  294.  
  295.     begin {GetStackTrace}
  296.         GetStackTrace := True;
  297.         outPtr := CharPtr(dest^);
  298.         outSize := 0;
  299.         i := startingDepth + 1;
  300.         repeat
  301.             stillOK := GetCallerInfo(i, aFrame, aName, anOffset);
  302.             if stillOK then
  303.                 begin
  304.                     aNameLength := length(aName);
  305.                     if outSize + aNameLength + addSize < destSize then
  306.                         begin
  307.                             MakeHex(aFrame, CharsPtr(outPtr), 8);
  308.                             outPtr := CharPtr(ORD(outPtr) + 8);
  309.  
  310.                             outPtr^ := ORD(' ');
  311.                             outPtr := CharPtr(ORD(outPtr) + SIZEOF(SignedByte));
  312.  
  313.                             BlockMove(@aName[1], Ptr(outPtr), aNameLength);
  314.                             outPtr := CharPtr(ORD(outPtr) + aNameLength);
  315.  
  316.                             outPtr^ := ORD('+');
  317.                             outPtr := CharPtr(ORD(outPtr) + SIZEOF(SignedByte));
  318.  
  319.                             MakeHex(anOffset, CharsPtr(outPtr), 4);
  320.                             outPtr := CharPtr(ORD(outPtr) + 4);
  321.  
  322.                             outPtr^ := 13;
  323.                             outPtr := CharPtr(ORD(outPtr) + SIZEOF(SignedByte));
  324.  
  325.                             outSize := ORD(outPtr) - ORD(dest^);
  326.                         end
  327.                     else
  328.                         begin
  329.                             GetStackTrace := False;
  330.                             Leave;
  331.                         end;
  332.                 end
  333.             else
  334.                 Leave;
  335.             i := i + 1;
  336.         until False;
  337.         destSize := outSize;
  338.     end; {GetStackTrace}
  339.  
  340. end.